package com.xiashitech.agent.instrumentation.struts2;

import com.xiashitech.agent.config.StartConfig;
import com.xiashitech.agent.dto.UserRequest;
import com.xiashitech.agent.instrumentation.base.AbstractAttributesExtractor;
import com.xiashitech.agent.instrumentation.spring.controller.apicapture.cache.ApiCache;
import com.xiashitech.agent.instrumentation.spring.controller.apicapture.inst.utils.GenerateSpanAttrUtil;
import com.xiashitech.agent.instrumentation.spring.refresh.api.inst.XSSpringMVCAllApiDeal;
import com.xiashitech.agent.instrumentation.struts2.api.inst.XSStruts2AllApiDeal;
import com.xiashitech.agent.utils.SessionUtil;
import com.xiashitech.interfaces.agent.dto.SystemInfoDTO;
import com.xiashitech.interfaces.agent.dto.api.*;
import io.opentelemetry.javaagent.shaded.io.opentelemetry.api.baggage.Baggage;
import io.opentelemetry.javaagent.shaded.io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.javaagent.shaded.io.opentelemetry.context.Context;
import org.jetbrains.annotations.Nullable;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;

public class XSStruts2ActionAttributesExtractor extends AbstractAttributesExtractor<Object, Object> {
    public String getAttributesExtractorName() {
        return XSStruts2ActionAttributesExtractor.class.getName();
    }
    @Override
    public void onXSStart(AttributesBuilder attributesBuilder, Context context, Object configurationProvider) {
    }

    @Override
    public void onXSEnd(AttributesBuilder attributesBuilder, Context context, Object packageConfig, @Nullable Object  userRequest, @Nullable Throwable throwable) {
        try {
            if(packageConfig != null) { // enter
                // 判断是否是用户定义的 Action 的 package，通过xml 是否在 jar 包中进行判断
                // TODO 对于 jar 包方式部署的 struts 应用，估计还需要考虑识别方式
                Object loc = packageConfig.getClass().getMethod("getLocation").invoke(packageConfig);
                if (loc == null) {
                    return;
                }
                String uri = (String) loc.getClass().getMethod("getURI").invoke(loc);
                if (uri == null || uri.contains(".jar!")) { // 异常或者非用户自定义的 Action
                    return;
                }
                List<Api> apis = XSStruts2AllApiDeal.extractApi(packageConfig);
                XSSpringMVCAllApiDeal.syncApiToOpenMetadata(apis);
            }
            try {
                if(userRequest != null && userRequest instanceof UserRequest) {
                    HttpServletRequest req = (HttpServletRequest) ((UserRequest) userRequest).getRequest();
                    if(req == null) { // 应该是在 Proxy 插桩
                        Object proxy = ((UserRequest) userRequest).getAnyObject();
                        if(proxy == null)
                            return;
                        Api api = new Api();
                        String postion = ((UserRequest) userRequest).getPosition(); // 在 execute 插桩时，response content-type 才有值
                        String actionName = (String) proxy.getClass().getMethod("getActionName").invoke(proxy); // 实际请求URL 中的 actionName，如：getallTblAppointment
                        String actionMethod = (String) proxy.getClass().getMethod("getMethod").invoke(proxy);   // 如：getall
                        String nameSpace = (String) proxy.getClass().getMethod("getNamespace").invoke(proxy);   // 如： /admin
                        Object actionObject = proxy.getClass().getMethod("getAction").invoke(proxy);
                        String className = actionObject.getClass().getName();  // 如：com.action.TblAppointmentAction
                        // 这方式没传成功，看日志，多次调用、顺序也不想预期情况
                        // Java8Bridge.currentForBaggage().toBuilder()
                        //         .put("xs.actionCompositeName", className+":"+nameSpace+"="+actionName+"#"+actionMethod)
                        //         .build().makeCurrent();;
                        // api.setCompositeName(className+":"+nameSpace+"="+actionName+"#"+actionMethod);
                        api.setActionClassName(className);
                        api.setActionName(actionName);
                        api.setActionNamespace(nameSpace);
                        api.setMethodName(actionMethod);
                        // 拿到 ActionConfig，便于构建一致的 Path
                        Object actionConfig = proxy.getClass().getMethod("getConfig").invoke(proxy);
                        String cfgActionName = (String)actionConfig.getClass().getMethod("getName").invoke(actionConfig); // 如：*TblAppointment
                        String cfgMethodName = (String)actionConfig.getClass().getMethod("getMethodName").invoke(actionConfig); // 不再是配置中 method 属性，而是计算后的结果，如：getall
                        String beanName = (String)actionConfig.getClass().getMethod("getClassName").invoke(actionConfig); // 如：tblAppointmentAction
                        String packageName = (String)actionConfig.getClass().getMethod("getPackageName").invoke(actionConfig); // 如：default

                        Object request = Class.forName("org.apache.struts2.ServletActionContext").getMethod("getRequest").invoke(null);
                        if (request != null && Class.forName("javax.servlet.http.HttpServletRequest").isAssignableFrom(request.getClass())) {
                            HttpServletRequest reqLocal = (HttpServletRequest) request;
                            String httpMethod = reqLocal.getMethod();
                            if (httpMethod != null && httpMethod != "") {
                                List<HttpMethod> listHttpMethod = new ArrayList<>();
                                listHttpMethod.add(HttpMethod.builder().name(httpMethod).build());
                                api.setHttpMethods(listHttpMethod);
                            }
                            String reqContentType = reqLocal.getContentType();
                            if (reqContentType == null || reqContentType == "") {
                                reqContentType = "application/x-www-form-urlencoded";
                            }
                            List<Consumes> consumesList = new ArrayList<>();
                            consumesList.add(Consumes.builder().name(reqContentType).build());
                            api.setConsumes(consumesList);

                            String uri = reqLocal.getRequestURI();
                            if (uri != null && uri != "") {
                                List<Path> pathList = new ArrayList<>();
                                pathList.add(Path.builder().name(uri).build());
                                api.setPaths(pathList);

                                String partPath = "/";
                                if(nameSpace.endsWith("/"))
                                    partPath = "";
                                String methodSeperate = "!";
                                if(actionMethod == null || "".equals(actionMethod))
                                    methodSeperate = "";
                                pathList.add(Path.builder().name(nameSpace + partPath + cfgActionName + ":" + beanName + ":" + className + methodSeperate + actionMethod).build());
                            }
                            // TODO request params
                            List<Params> paramsList = new ArrayList<>();
                            api.setRequestParams(paramsList);
                        }

                        Object response = Class.forName("org.apache.struts2.ServletActionContext").getMethod("getResponse").invoke(null);
                        if (response != null && Class.forName("javax.servlet.http.HttpServletResponse").isAssignableFrom(response.getClass())) {
                            HttpServletResponse resp = (HttpServletResponse) response;
                            String respContentType = resp.getContentType(); // 这里取不到值，可能位置不太对，还是对象不是一个？
                            if (respContentType == null || respContentType == "") {
                                respContentType = "text/html; charset=utf-8";
                            }
                            List<Produces> producesList = new ArrayList<>();
                            producesList.add(Produces.builder().name(respContentType).build());
                            api.setProduces(producesList);
                            // TODO response params
                            Params p = Params.builder().name("Object").type("Object").build();
                            List<Params> paramsList = new ArrayList<>();
                            paramsList.add(p);
                            api.setResponseParams(paramsList);
                        }
                        List<Api> apis = new ArrayList<>();
                        apis.add(api);
                        XSSpringMVCAllApiDeal.syncApiToOpenMetadata(apis);

                        // 加入 trace
                        Baggage baggage = Baggage.current();
                        UserRequest ur = (UserRequest)userRequest;
                        if ((ur.getUserid() == null || "".equals(ur.getUserid())) && baggage.size() > 0) {
                            ur.setUserid(baggage.getEntryValue("xs.userId"));
                            ur.setRequestDate(baggage.getEntryValue("xs.requestDate"));
                            ur.setSessionId(baggage.getEntryValue("xs.sessionId"));
                            ur.setSessionUpdate(baggage.getEntryValue("xs.sessionUpdate") == null ? false : Boolean.valueOf(baggage.getEntryValue("xs.sessionUpdate")));
                        }
                        if (baggage.size() > 0 && baggage.getEntryValue("xs.loginName") != null) {
                            ur.setLoginName(baggage.getEntryValue("xs.loginName"));
                        }
                        setAttributes(attributesBuilder, ur);
                        Method m = actionObject.getClass().getMethod(actionMethod);
                        String methodSign = GenerateSpanAttrUtil.getMethodSign(actionObject.getClass(), m);
                        if(!ApiCache.cache.containsKey(methodSign))
                            ApiCache.cache.put(methodSign, api);
                        GenerateSpanAttrUtil.generateSpanAttr(ur, attributesBuilder, actionObject.getClass(), m);
                        String endpointApiIsNull = baggage.getEntryValue("endpoint-api");
                        if (endpointApiIsNull == null) {
                            Api cachedApi = ApiCache.cache.get(methodSign);
                            StringJoiner stringJoiner = new StringJoiner(",");
                            cachedApi.getPaths().forEach(apiPath -> stringJoiner.add(apiPath.getName()));
                            String endpointApi = stringJoiner.toString();
                            ur.setEndpointApi(endpointApi);
                            ur.setApiSystemId(StartConfig.getParamValueFromKey(StartConfig.systemId));
                            ur.setIsOutApi(Boolean.TRUE);
                        }
                    } else {// 表示在 serviceAction 插桩
                        // HttpServletResponse resp = (HttpServletResponse) ((UserRequest) userRequest).getResponse();
                        // if(resp != null) {
                        //     String  contentType = resp.getContentType(); // 这里是能取到值的
                        //     System.out.println("contentType: " + contentType);
                        // }
                    }
                }
            } catch (Throwable ee) {
                StartConfig.throwableStackPrinter(ee);
            }
        } catch (Throwable e) {
            StartConfig.throwableStackPrinter(e);
        }
    }

    private void setAttributes(AttributesBuilder attributesBuilder, UserRequest userRequest) {
        attributesBuilder.put("xs.userId",userRequest.getUserid());
        attributesBuilder.put("xs.requestDate",userRequest.getRequestDate());
        attributesBuilder.put("xs.sessionUpdate",userRequest.getSessionUpdate());
        attributesBuilder.put("xs.systemId", StartConfig.getParamValueFromKey(StartConfig.systemId));
        attributesBuilder.put("xs.systemReg", SystemInfoDTO.getConfig().getSystemExist());
        attributesBuilder.put("xs.sourceSystemId", Baggage.current().getEntryValue("xsAuthSystemId") == null ? StartConfig.getParamValueFromKey(StartConfig.systemId) : Baggage.current().getEntryValue("xsAuthSystemId"));
        SessionUtil.syncBaggage().toBuilder()
                .put("xs.userId",userRequest.getUserid())
                .put("xs.requestDate",userRequest.getRequestDate())
                .put("xs.sessionUpdate",userRequest.getSessionUpdate() == null ? "false" : userRequest.getSessionUpdate().toString())
                .build().makeCurrent();
    }
}
